﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UCS.ArtificialInteligence.MiniMax
{
    public class MiniMaxSearch
    {
        #region Private Members

        private int _maxDepth;
        private bool _useAlphaBetaPruning;

        #endregion

        #region Constructors

        public MiniMaxSearch(int maxDepth, bool useAlphaBetaPruning)
        {
            _maxDepth = maxDepth;
            _useAlphaBetaPruning = useAlphaBetaPruning;
        }

        #endregion

        #region Public Methods

        public IMiniMaxProblemState GetNextMovement(IMiniMaxProblemState initialState)
        {
            if (initialState.PlayerType == MiniMaxPlayerType.Min)
                throw new Exception("Initial player type should be Max.");

            if (_useAlphaBetaPruning)
                return PerformAlphaBetaPruningSearch(initialState);
            else
                return PerformSearch(initialState);
        }

        #endregion

        #region Private Methods

        private IMiniMaxProblemState PerformSearch(IMiniMaxProblemState initialState)
        {
            int maxValue = int.MinValue;
            IMiniMaxProblemState maxState = null;
            foreach (var border in initialState.Borders)
            {
                int value = this.GetValue(border.State);

                if (value > maxValue)
                {
                    maxValue = value;
                    maxState = border.State;
                }
            }

            return maxState;
        }

        private int GetValue(IMiniMaxProblemState currentState)
        {
            if (currentState.Depth == _maxDepth)
                return currentState.HeuristicValue;

            if (currentState.PlayerType == MiniMaxPlayerType.Max)
                return this.GetMaxValue(currentState);
            else
                return this.GetMinValue(currentState);
        }

        private int GetMaxValue(IMiniMaxProblemState currentState)
        {
            int maxValue = int.MinValue;

            List<ProblemStateBorder> toRemoveBorders = new List<ProblemStateBorder>();
            foreach (var border in currentState.Borders)
            {
                int value = this.GetValue(border.State);
                toRemoveBorders.Add(border);

                if (value > maxValue)
                    maxValue = value;
            }

            currentState.ClearBorders();

            return maxValue;
        }

        private int GetMinValue(IMiniMaxProblemState currentState)
        {
            int minValue = int.MaxValue;
            foreach (var border in currentState.Borders)
            {
                int value = this.GetValue(border.State);

                if (value < minValue)
                    minValue = value;
            }

            currentState.ClearBorders();

            return minValue;
        }

        private IMiniMaxProblemState PerformAlphaBetaPruningSearch(IMiniMaxProblemState initialState)
        {
            int alpha = int.MinValue;
            int beta = int.MaxValue;
            IMiniMaxProblemState maxState = null;
            foreach (var border in initialState.Borders)
            {
                int value = this.GetValue(border.State, alpha, beta);

                if (value > alpha)
                {
                    alpha = value;
                    maxState = border.State;
                }

                if (alpha >= beta)
                    return maxState;
            }

            return maxState;
        }

        private int GetValue(IMiniMaxProblemState currentState, int alpha, int beta)
        {
            if (currentState.Depth == _maxDepth)
                return currentState.HeuristicValue;

            if (currentState.PlayerType == MiniMaxPlayerType.Max)
                return this.GetMaxValue(currentState, alpha, beta);
            else
                return this.GetMinValue(currentState, alpha, beta);
        }

        private int GetMaxValue(IMiniMaxProblemState currentState, int alpha, int beta)
        {
            List<ProblemStateBorder> toRemoveBorders = new List<ProblemStateBorder>();
            foreach (var border in currentState.Borders)
            {
                int value = this.GetValue(border.State);
                toRemoveBorders.Add(border);

                if (value > alpha)
                    alpha = value;

                if (alpha >= beta)
                {
                    currentState.ClearBorders();
                    return beta;
                }
            }

            currentState.ClearBorders();

            return alpha;
        }

        private int GetMinValue(IMiniMaxProblemState currentState, int alpha, int beta)
        {
            foreach (var border in currentState.Borders)
            {
                int value = this.GetValue(border.State);

                if (value < beta)
                    beta = value;

                if (beta <= alpha)
                {
                    currentState.ClearBorders();
                    return alpha;
                }
            }

            currentState.ClearBorders();

            return beta;
        }

        #endregion
    }
}
